병원 검진 예약 사이트 만들기

✒️ 2025-07-06 21:34 내용 수정


실습 목표

  1. 병원 예약 관리 시스템을 만들어 예약 목록을 조회하고 보안 기능을 추가한다.
  2. Reservation Entity 정보
이름 타입 설명
reservationId int 예약 ID
patientName String 환자명
patientPhone String 환자 연락처
doctorName String 의사명
department String 진료과
reservationDate Date 예약 날짜
reservationTime Time 예약 시간
symptoms String 증상
status String 예약 상태
createdAt Timestamp 등록일시
  1. 보안 요구 사항
    • XSS 방지를 위한 출력값 이스케이프 처리
    • 전화번호 마스킹
    • SQL Injection 방지를 위한 PreparedStatement 사용

프로젝트 설정

의존성

<!-- JSP API -->  
<dependency>  
	<groupId>javax.servlet.jsp</groupId>  
	<artifactId>javax.servlet.jsp-api</artifactId>  
	<version>2.3.3</version>  
	<scope>provided</scope>  
</dependency>  

<!-- JSTL -->  
<dependency>  
	<groupId>javax.servlet</groupId>  
	<artifactId>jstl</artifactId>  
	<version>1.2</version>  
</dependency>

<!-- PostgreSQL JDBC Driver --> 
<dependency> 
	<groupId>org.postgresql</groupId> 
	<artifactId>postgresql</artifactId> 
	<version>42.6.0</version> 
</dependency>

디렉터리 구조

reservation/
├── src/
│   ├── main/
│       ├── java/
│       │   ├── dao/
│       │   │   └── ReservationDAO.java
│       │   ├── dto/
│       │   │   └── Reservation.java
│       │   ├── servlet/
│       │   │   └── ReservationListServlet.java
│       │   ├── util/
│       │       ├── DBConnection.java
│       │       └── SecurityUtil.java
│       ├── resources/
│       ├── webapp/
│           ├── WEB-INF/
│           │   ├── views/
│           │   │   ├── error.jsp
│           │   │   └── reservationList.jsp
│           │   └── web.xml
│           └── index.jsp
└── pom.xml

database 설정

-- 데이터베이스 생성
CREATE DATABASE hospital_reservation_system;

-- reservations 테이블 생성
CREATE TABLE reservations (
    reservation_id SERIAL PRIMARY KEY,
    patient_name VARCHAR(100) NOT NULL,
    patient_phone VARCHAR(20) NOT NULL,
    doctor_name VARCHAR(100) NOT NULL,
    department VARCHAR(50) NOT NULL,
    reservation_date DATE NOT NULL,
    reservation_time TIME NOT NULL,
    symptoms TEXT,
    status VARCHAR(20) DEFAULT '예약완료' CHECK (status IN ('예약완료', '진료완료', '취소')),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 초기 예약 데이터 삽입
INSERT INTO reservations (patient_name, patient_phone, doctor_name, department, reservation_date, reservation_time, symptoms, status) VALUES 
('김환자', '010-1234-5678', '김내과', '내과', '2025-06-20', '09:00', '복통 및 소화불량 증상', '예약완료'),
('이환자', '010-2345-6789', '이외과', '외과', '2025-06-20', '10:30', '복부 종양 검사', '예약완료'),
('박환자', '010-3456-7890', '박정형', '정형외과', '2025-06-21', '14:00', '무릎 통증', '진료완료'),
('최환자', '010-4567-8901', '최피부', '피부과', '2025-06-22', '16:30', '피부 트러블', '예약완료'),
('정환자', '010-5678-9012', '정안과', '안과', '2025-06-23', '11:00', '시력 저하', '취소'),
('한환자', '010-6789-0123', '김내과', '내과', '2025-06-24', '15:00', '정기 검진', '예약완료');

-- 데이터 확인
SELECT * FROM reservations ORDER BY reservation_date, reservation_time;

MVC 패턴

Util

DB Connection

package util;  
  
import java.sql.Connection;  
import java.sql.DriverManager;  
  
public class DBConnection {  
  
    private static final String url = "jdbc:postgresql://localhost:5432/hospital_reservation_system";  
    private static final String user = "postgres";  
    private static final String password = "password";  
  
    static {  
        try {  
	        // 드라이버 클래스 탐색
            Class.forName("org.postgresql.Driver");  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
        }  
    }  
  
    public static Connection getConnection() throws Exception {  
        return DriverManager.getConnection(url, user, password);  
    }  
  
}

SecurityUtil

  1. HTML 이스케이프
    • sanitize-html 라이브러리처럼 escapeHtml에는 input을 받아서 HTML 태그 요소에 해당하는 <> 등의 기호를 HTML 엔티티 코드로 처리한다.
    • removeScripts 함수는 SCRIPT_PATTERN으로 지정한 스크립트 태그 정규 표현식과 일치하는 String 내용물을 지운다.
    • sanitizeString 함수에선 위 두 함수를 사용해서 스크립트 태그를 제거하고, HTML 태그가 될 수 있는 기호들을 모두 엔티티 코드로 처리해서 스크립트가 들어오지 않도록 만든다.
  2. 전화번호 마스킹
    • 전화번호 자릿수에 따라 중간 번호 부분을 * 표시로 처리한다.
package util;  
  
import java.util.regex.Pattern;  
  
public class SecurityUtil {  
    private static final Pattern HTML_TAG_PATTERN = Pattern.compile("<[^>]*>");  
    private static final Pattern SCRIPT_PATTERN = Pattern.compile("(?i)<script[^>]*>.*?</script>");  
  
    public static String escapeHtml(String input) {  
        if (input == null) return null;  
  
        return input.replace("&", "&amp;")  
                .replace("<", "&lt;")  
                .replace(">", "&gt;")  
                .replace("\"", "&quot;")  
                .replace("'", "&#x27;")  
                .replace("/", "&#x2F;");  
    }  
  
    public static String removeScripts(String input) {  
        if (input == null) return null;  
        return SCRIPT_PATTERN.matcher(input).replaceAll("");  
    }  
  
    public static String sanitizeString(String input) {  
        if (input == null) return null;  
        String scriptRemovedInput = removeScripts(input);  
        return escapeHtml(scriptRemovedInput);  
    }  
  
    public static String maskPhoneNumber(String phone) {  
        if (phone == null || phone.length() < 8) return phone;  
  
        String cleaned = phone.replaceAll("[^0-9]", "");  
        if (cleaned.length() == 11) {  
            return cleaned.substring(0, 3) + "-****-" + cleaned.substring(7);  
        } else if (cleaned.length() == 10) {  
            return cleaned.substring(0, 3) + "-***-" + cleaned.substring(6);  
        }  
        return phone;  
    }  
}

Model

DTO

package dto;  
  
import java.sql.Time;  
import java.sql.Date;  
import java.sql.Timestamp;  
  
public class Reservation {  
    private int reservationId;  
    private String patientName;  
    private String patientPhone;  
    private String doctorName;  
    private String department;  
    private Date reservationDate;  
    private Time reservationTime;  
    private String symptoms;  
    private String status;  
    private Timestamp createdAt;  
  
    public Reservation() {}  
    public Reservation(
	    int reservationId, String patientName, String patientPhone, 
	    String doctorName, String department, Date reservationDate, 
	    Time reservationTime, String symptoms, 
	    String status, Timestamp createdAt
	) {  
        this.reservationId = reservationId;  
        this.patientName = patientName;  
        this.patientPhone = patientPhone;  
        this.doctorName = doctorName;  
        this.department = department;  
        this.reservationDate = reservationDate;  
        this.reservationTime = reservationTime;  
        this.symptoms = symptoms;  
        this.status = status;  
        this.createdAt = createdAt;  
    }  

	// setter와 getter
}

DAO

package dao;  
  
import dto.Reservation;  
import util.DBConnection;  
  
import java.sql.Connection;  
import java.sql.PreparedStatement;  
import java.sql.ResultSet;  
import java.util.ArrayList;  
import java.util.List;  
  
public class ReservationDAO {  
    private static ReservationDAO instance = new ReservationDAO();  
  
    private ReservationDAO() {}  
  
    public static ReservationDAO getInstance() {  
        return instance;  
    }  
  
    public List<Reservation> selectList() {  
        List<Reservation> list = new ArrayList<>();  
        String sql = "SELECT * FROM reservations";  
  
        try {  
            Connection conn = DBConnection.getConnection();  
            PreparedStatement pstmt = conn.prepareStatement(sql);  
            ResultSet rs = pstmt.executeQuery();  
  
            while(rs.next()) {  
                Reservation rev = new Reservation();  
                rev.setReservationId(rs.getInt("reservation_id"));  
                rev.setPatientName(rs.getString("patient_name"));  
                rev.setPatientPhone(rs.getString("patient_phone"));  
                rev.setDoctorName(rs.getString("doctor_name"));  
                rev.setDepartment(rs.getString("department"));  
                rev.setReservationDate(rs.getDate("reservation_date"));  
                rev.setReservationTime(rs.getTime("reservation_time"));  
                rev.setSymptoms(rs.getString("symptoms"));  
                rev.setStatus(rs.getString("status"));  
                rev.setCreatedAt(rs.getTimestamp("created_at"));  
                list.add(rev);  
            }  
  
            rs.close();  
            pstmt.close();  
            conn.close();  
        } catch (Exception e){  
            e.printStackTrace();  
        }  
  
        return list;  
    }  
}

Controller

Servlet

package servlet;  
  
import dao.ReservationDAO;  
import dto.Reservation;  
import util.SecurityUtil;  
  
import javax.servlet.RequestDispatcher;  
import javax.servlet.ServletException;  
import javax.servlet.annotation.WebServlet;  
import javax.servlet.http.HttpServlet;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import java.io.IOException;  
import java.util.ArrayList;  
import java.util.List;  
  
@WebServlet("/reservations")  
public class ReservationListServlet extends HttpServlet {  
    private static final long serialVersionUID = 1L;  
  
    @Override  
    protected void service(HttpServletRequest request, HttpServletResponse response)  
    throws ServletException, IOException {  
        try {  
            ReservationDAO dao = ReservationDAO.getInstance();  
            List<Reservation> list = dao.selectList();  
            List<Reservation> securedList = new ArrayList<>();  
            
            // 데이터 가져오기
            for(Reservation res : list) {  
                Reservation secRes = new Reservation(  
                        res.getReservationId(),  
                        SecurityUtil.sanitizeString(res.getPatientName()),  
                        SecurityUtil.maskPhoneNumber(res.getPatientPhone()),  
                        SecurityUtil.sanitizeString(res.getDoctorName()),  
                        SecurityUtil.sanitizeString(res.getDepartment()),  
                        res.getReservationDate(),  
                        res.getReservationTime(),  
                        SecurityUtil.sanitizeString(res.getSymptoms()),  
                        SecurityUtil.sanitizeString(res.getStatus()),  
                        res.getCreatedAt()  
                );  
                securedList.add(secRes);  
            }  
            request.setAttribute("list", securedList);  
            RequestDispatcher disp = request.getRequestDispatcher("/WEB-INF/views/reservationList.jsp");  
            disp.forward(request, response);  
        } catch (Exception e) {  
            e.printStackTrace();  
            request.setAttribute("errorMessage", "데이터를 가져오는 데 실패했습니다.");  
            RequestDispatcher disp = request.getRequestDispatcher("/WEB-INF/views/error.jsp");  
            disp.forward(request, response);  
        }  
    }  
}

View

main

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"  
 errorPage="error.jsp"  
%>  
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>  
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>  
<!doctype html>  
<html lang="ko">  
<head>  
    <meta charset="UTF-8">  
    <title>예약 목록</title>  
    <style>        
	    table, tr, th, td{  
            border-collapse: collapse;  
            border : 1px solid black;  
            text-align: center;  
        }  
        th {  
        background-color: #f1f1f1;  
        }  
        tr, th, td {  
            padding: 5px;  
        }  
    </style>  
</head>  
<body>  
    <table>  
        <tr>  
            <th>예약ID</th>  
            <th>환자명</th>  
            <th>환자 연락처</th>  
            <th>의사명</th>  
            <th>진료과</th>  
            <th>예약날짜</th>  
            <th>예약시간</th>  
            <th>증상</th>  
            <th>예약상태</th>  
            <th>등록일시</th>  
        </tr>  
        <c:forEach var="rev" items="${list}">  
            <tr>  
                <td>${rev.reservationId}</td>  
                <td>${rev.patientName}</td>  
                <td>${rev.patientPhone}</td>  
                <td>${rev.doctorName}</td>  
                <td>${rev.department}</td>  
                <td>${rev.reservationDate}</td>  
                <td>${rev.reservationTime}</td>  
                <td>${rev.symptoms}</td>  
                <td>${rev.status}</td>  
                <td><fmt:formatDate value="${rev.createdAt}" 
                pattern="yyyy-MM-dd HH:mm"/></td>  
            </tr>  
        </c:forEach>  
    </table>  
</body>  
</html>

error page

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  
isErrorPage="true" isELIgnored="false"  
%>  
<!doctype html>  
<html lang="ko">  
<head>  
    <meta charset="UTF-8">  
    <title>에러 페이지</title>  
</head>  
<body>  
    <h2>에러가 발생했습니다</h2>  
    <p>${errorMessage}</p>  
</body>  
</html>

테스트

jsp_reservation_test.png